package edu.amm.utils;

/**
 * Утилиты для работы с алгебраическими объектами.
 * 
 * @author Иванов Илья
 * @since 2014.03.10
 *
 */
public class AlgebraUtils {
	
	/**
	 * Копирует матрицы.
	 * 
	 * @param source Исходная матрица.
	 * @param destination Целевая матрица.
	 */
	public static void copyMatrix(double[][] source, double[][] destination) {
		if (!checkMatrix(source) || !checkMatrix(destination) ||
			source.length != destination.length || source[0].length != destination[0].length) {
			throw new RuntimeException("Некорректные матрицы для копирования");
		}
		
		for (int i = 0; i < source.length; i++) {
			for (int j = 0; j < source[i].length; j++) {
				destination[i][j] = source[i][j];
			}
		}
	}
	
	/**
	 * Проверяет, является ли двумерный массив прямоугольной матрицей.
	 * 
	 * @param matrix Массив для проверки.
	 * @return {@code true}, если двумерный массив является прямоугольной матрицей.
	 */
	public static boolean checkMatrix(double[][] matrix) {
		if (matrix == null || matrix.length == 0 || matrix[0] == null) {
			return false;
		}
		
		int columns = matrix[0].length;
		for (int i = 1; i < matrix.length; i++) {
			if (matrix[i] == null || matrix[i].length != columns) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * Вычисляет евклидову норму вектора.
	 * 
	 * @param vector Вектор.
	 * @return Евклидова норма вектора.
	 */
	public static double euclideanNorm(double[] vector) {
		if (vector == null || vector.length == 0) {
			throw new RuntimeException("Некорректный вектор для вычисления евклидовой нормы");
		}
		
		double norm = 0;
		for (int i = 0; i < vector.length; i++) {
			norm += vector[i] * vector[i];
		}
		
		norm = Math.sqrt(norm);
		return norm;
	}
	
	/**
	 * Нормирует вектор по евклидовой норме.
	 * 
	 * @param vector Вектор для нормировки.
	 */
	public static void normalize(double[] vector) {
		if (vector == null || vector.length == 0) {
			throw new RuntimeException("Некорректный вектор для нормировки");
		}
		
		double norm = euclideanNorm(vector);
		if (norm == 0) {
			return;
		}
		
		for (int i = 0; i < vector.length; i++) {
			vector[i] /= norm;
		}
	}
	
	/**
	 * Нормирует матрицу по строкам.
	 * 
	 * @param matrix Матрица для нормировки.
	 */
	public static void normalizeByRows(double[][] matrix) {
		if (!checkMatrix(matrix)) {
			throw new RuntimeException("Некорректная матрица для нормировки по строкам");
		}
		
		for (int i = 0; i < matrix.length; i++) {
			normalize(matrix[i]);
		}
	}
	
	/**
	 * Нормирует матрицу по столбцам.
	 * 
	 * @param matrix Матрица для нормировки.
	 */
	public static void normalizeByColumns(double[][] matrix) {
		if (!checkMatrix(matrix)) {
			throw new RuntimeException("Некорректная матрица для нормировки по столбцам");
		}
		
		for (int j = 0; j < matrix[0].length; j++) {
			double norm = 0;
			for (int i = 0; i < matrix.length; i++) {
				norm += matrix[i][j] * matrix[i][j];
			}
			
			if (norm == 0) {
				continue;
			}
			
			norm = Math.sqrt(norm);
			
			for (int i = 0; i < matrix.length; i++) {
				matrix[i][j] /= norm;
			}
		}
	}
	
	/**
	 * Вычисляет расстояние между двумя векторами.
	 * 
	 * @param first Первый вектор.
	 * @param second Второй вектор.
	 * @return Расстояние между двумя векторами.
	 */
	public static double distance(double[] first, double[] second) {
		if (first == null || second == null || first.length == 0 || first.length != second.length) {
			throw new RuntimeException("Некорректные векторы для вычисления расстояния");
		}
		
		double dist = 0;
		for (int i = 0; i < first.length; i++) {
			double diff = first[i] - second[i];
			dist += diff * diff;
		}
		
		dist = Math.sqrt(dist);
		return dist;
	}
}
